package com.a0soft.gphone.aTruffleHog.di;
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * Copyright(C) 2011 
 *   Alger Lin <addre75 (at) gmail.com>
 *
 */

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.a0soft.gphone.aTruffleHog.util.CONSTS;
import com.a0soft.gphone.aTruffleHog.util.aDebug;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;


/**Agent to collects information about bluetooth.*/
public class BtInfo extends BaseInfo {
    /**Tag for log.*/
    private static final String LOG_TAG = BtInfo.class.getSimpleName();
    /**Group of bluetooth capability.<p>
     * Data at this group be updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROUP_CAPABILITY = 0;
    /**Group of wifi connection.<p>
     * Data at this group not updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROP_CONNECT = 1;
    /**Maximum count of group.*/
    private static final int    GROUP_MAX = 2;
    /**Group name.<p>
     * Elements' index follows GROUP_xxx definition*/
    private static final String GROUP_NAME[] =
    {
        "Bluetooth capability",  //GROUP_CAPABILITY
        "Bluetooth connection",  //GROP_CONNECT
    };
    /**Format-able string of bonded bluetooth device item.*/
    private static final String STR_DEV = "Device - %s"; 
    /**Key of bluetooth available states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_BT_AVA = DeviceInfoKey.GetName(DeviceInfoKey.K.BT_AVA);
    /**Key of interface at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_INTERFACE = DeviceInfoKey.GetName(DeviceInfoKey.K.BT_INTERFACE);
    /**Key of name at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_NAME = BtDevHelper.KEY_NAME;
    /**Key of address at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_ADDR = BtDevHelper.KEY_ADDR;
    /**Key of states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_STATE = "State";
    /**Key of discovering timeout at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_TIMEOUT = "Discovery timeout";
    /**Key of scan mode at GROP_CONNECT.*/
    private static final String KEY_CONNECT_SCAN = "Scan mode";
    /**Key of connection mode at GROP_CONNECT.*/
    private static final String KEY_CONNECT_CONN = "Connection";
    /**Key of connection mode for A2DP devices at GROP_CONNECT.*/
    private static final String KEY_CONNECT_A2DP = "A2DP conn.";
    /**Key of connection mode for headset devices at GROP_CONNECT.*/
    private static final String KEY_CONNECT_HEADSET = "Headset conn.";
    /**Key of connection mode for health devices at GROP_CONNECT.*/
    private static final String KEY_CONNECT_HEALTH = "Health conn.";
    /**Hidden class LinkProperties for wrap properties for linking.*/
    private static Class<?>     m_oClsProp = null;
    /**Hidden method in ConnectivityManager to query linking properties.*/
    private static Method       m_oMetProp = null;
    /**Hidden method in LinkProperties to get interface name of linking.*/
    private static Method       m_oMetInterface = null;
    /**Hidden method in BluetoothAdapter to query connection status.*/
    private static Method       m_oMetConn = null;
    /**Hidden method in BluetoothAdapter to query timeout period for discovering.*/
    private static Method        m_oMetTimeout = null;
    /**Method to query connection state of profile at API 14 and later.*/
    private static Method        m_oMetProfConn = null;
    /**Flag to indicates hidden API loaded.*/
    private static boolean      m_bLoadApi = false;
        
    
    /**Build information of sensors.
     * @param di        [out] Repository of device information.
     * @param context   [in] Application environment.*/
    static void
    Build(DeviceInfo di, Context context)
    {
        getBtData(context, GROUP_CAPABILITY, di);
    }
    
    /**Get human-readable group name.
     * @param group [in] Identify of group, this parameter should be one of GROUP_xxx definition.
     * @return      Return name of group if succeeded, return null if failed.*/
    public static String getBtDataGroupName(int group)
    {
        String  returnObj = null;
        if((group < 0) || (group >= GROUP_MAX)) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, String.format("Invalid group: %d", group));
        }
        else 
            returnObj = GROUP_NAME[group];
        return returnObj;
    }
    
    /**Get human-readable bonded bluetooth device name.
     * @param oDev  [in] Data set of bluetooth device.
     * @return      Return name of bluetooth device if succeeded, return null if failed.*/
    public static String getBtDevName(HashMap<String, String> oDev)
    {
        String  returnObj = null;
        if(oDev == null) {
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without data set");
        } 
        else if(!oDev.containsKey(BtDevHelper.KEY_NAME)) {
            returnObj = String.format(STR_DEV, CONSTS.UNKNOWN);
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without SSID");
        }
        else 
            returnObj = String.format(STR_DEV, oDev.get(BtDevHelper.KEY_NAME));
        return returnObj;
    }
    
    /**Get bluetooth adapter.<p>
     * This function launch BluetoothAdapter.getDefaultAdapter with try/catch block, to deal with 
     * an exception which caused by not called at main thread in HTC Flyer (Android 3.2)
     * @return  Return bluetooth adapter if succeeded, return null if failed.*/
    private static BluetoothAdapter getBtAdapter()
    {
        BluetoothAdapter    returnObj = null;
        try {
            returnObj = BluetoothAdapter.getDefaultAdapter();
        }
        catch(Exception ex) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, null, ex);
        }
        return returnObj;
    }
    
    /**Get bluetooth data by group set.
     * @param context   [in] Application environment.
     * @param group     [in] Identify of group, this parameter should be one of GROUP_xxx 
     *                  definition.
     * @param di        [out] Repository of device information. Set null if without repository to
     *                  feed.<p>
     *                  When this parameter set, only the data for server be feed.                   
     * @return          Return data set if succeeded, return null if not available.*/
    private static HashMap<String, String> getBtData(Context context, int group, DeviceInfo di)
    {
        HashMap<String, String> returnObj = null;
        BluetoothAdapter        oBt = null;
        loadHiddenApi();
        //Group for bluetooth capability 
        if(group == GROUP_CAPABILITY) {
            PackageManager      oPkg = context.getPackageManager();
            ConnectivityManager oConn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            //Clean device information repository
            if(di != null) {
                di.Put(DeviceInfoKey.K.BT_AVA, CONSTS.UNAVAILABLE);
                di.Put(DeviceInfoKey.K.BT_INTERFACE, CONSTS.UNKNOWN);
            }
            if((oPkg == null) || (oConn == null)) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, String.format("Get %s failed", ((oPkg == null)? "PackageManager": "ConnectivityManager")));
            }
            //Handle if unable to get bluetooth adapter
            else if((oBt = getBtAdapter()) == null) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Unable to get BluetoothAdapter");
            }
            else {
                NetworkInfo oInfo = null;
                String      szVal = null;
                int         iSta = 0;
                returnObj = new HashMap<String, String> ();
                //Set name
                if((di == null) && ((szVal = getName(oBt)) != null) && (szVal.length() > 0))
                    returnObj.put(KEY_CAPABILITY_NAME, szVal);
                //Set mac address             
                if((di == null) && ((szVal = getAddress(oBt)) != null) && (szVal.length() > 0) && isValidMacAddress(szVal))
                    returnObj.put(KEY_CAPABILITY_ADDR, szVal);
                //Set bluetooth capability by public API
                if(oPkg != null)
                {
                    String  szBt = String.valueOf(oPkg.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH));
                    returnObj.put(KEY_CAPABILITY_BT_AVA, szBt);
                    if(di != null)
                        di.Put(DeviceInfoKey.K.BT_AVA, szBt);
                }
                //Set states
                if((di == null) && ((iSta = getBtState(oBt)) >= 0)) {
                    switch(iSta) {
                        case BluetoothAdapter.STATE_CONNECTED:
                            returnObj.put(KEY_CAPABILITY_STATE, "connected");
                            break;
                        case BluetoothAdapter.STATE_CONNECTING:
                            returnObj.put(KEY_CAPABILITY_STATE, "connecting");
                            break;
                        case BluetoothAdapter.STATE_DISCONNECTED:
                            returnObj.put(KEY_CAPABILITY_STATE, "disconnected");
                            break;
                        case BluetoothAdapter.STATE_DISCONNECTING:
                            returnObj.put(KEY_CAPABILITY_STATE, "disconnecting");
                            break;
                        case BluetoothAdapter.STATE_OFF:
                            returnObj.put(KEY_CAPABILITY_STATE, "off");
                            break;
                        case BluetoothAdapter.STATE_ON:
                            returnObj.put(KEY_CAPABILITY_STATE, "on");
                            break;
                        case BluetoothAdapter.STATE_TURNING_OFF:
                            returnObj.put(KEY_CAPABILITY_STATE, "turning off");
                            break;
                        case BluetoothAdapter.STATE_TURNING_ON:
                            returnObj.put(KEY_CAPABILITY_STATE, "turning on");
                            break;
                        default:
                            returnObj.put(KEY_CAPABILITY_STATE, String.valueOf(iSta));
                            break;
                    }
                }
                //Set interface by hidden API
                if((m_oClsProp != null) && ((oInfo = oConn.getNetworkInfo(ConnectivityManager.TYPE_BLUETOOTH)) != null)) {
                    Object  oProp = null;
                    Object  oName = null;
                    Object  oPropSub = null;
                    Object  oNameSub = null;
                    try {
                        oProp = m_oMetProp.invoke(oConn, oInfo.getType());
                        oName = m_oMetInterface.invoke(oProp);
                        oPropSub = m_oMetProp.invoke(oConn, oInfo.getSubtype());
                        oNameSub = m_oMetInterface.invoke(oPropSub); 
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if(oName != null) {
                        szVal = ((oNameSub != null)? String.format("%s \\ %s", oName, oNameSub): oName.toString());
                        returnObj.put(KEY_CAPABILITY_INTERFACE, szVal);
                        if(di != null)
                            di.Put(DeviceInfoKey.K.WF_INTERFACE, szVal);
                    }
                }
                //Set discover timeout //This value shown if scan mode is SCAN_MODE_CONNECTABLE_DISCOVERABLE only
                if((di == null) && (m_oMetTimeout != null) && (getScanMode(oBt) == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)) {
                    Object  oVal = null;
                    int     iVal = -1;
                    try {
                        oVal = m_oMetTimeout.invoke(oBt);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Integer) && ((iVal = Integer.class.cast(oVal).intValue()) > 0))
                        returnObj.put(KEY_CAPABILITY_TIMEOUT, String.format("%d Sec(s)", iVal));
                }
            }
        }
        //Group for bluetooth connection
        else if(group == GROP_CONNECT) {
            if(di != null) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Bluetooth connection information not for server");
            }
            //Handle if unable to get bluetooth adapter
            else if((oBt = BluetoothAdapter.getDefaultAdapter()) == null) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Unable to get BluetoothAdapter");
            }
            else {
                int     iVal = 0;
                returnObj = new HashMap<String, String> ();
                //Set connection state
                if(m_oMetConn != null) {
                    Object oVal = null;
                    try {
                        oVal = m_oMetConn.invoke(oBt);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Integer)) {
                        switch(iVal = Integer.class.cast(oVal).intValue()) {
                            case BluetoothAdapter.STATE_CONNECTED:
                                returnObj.put(KEY_CONNECT_CONN, "connected");
                                break;
                            case BluetoothAdapter.STATE_CONNECTING:
                                returnObj.put(KEY_CONNECT_CONN, "connecting");
                                break;
                            case BluetoothAdapter.STATE_DISCONNECTED:
                                returnObj.put(KEY_CONNECT_CONN, "disconnected");
                                break;
                            case BluetoothAdapter.STATE_DISCONNECTING:
                                returnObj.put(KEY_CONNECT_CONN, "disconnecting");
                                break;
                            case BluetoothAdapter.STATE_OFF:
                                returnObj.put(KEY_CONNECT_CONN, "off");
                                break;
                            case BluetoothAdapter.STATE_ON:
                                returnObj.put(KEY_CONNECT_CONN, "on");
                                break;
                            case BluetoothAdapter.STATE_TURNING_OFF:
                                returnObj.put(KEY_CONNECT_CONN, "turning off");
                                break;
                            case BluetoothAdapter.STATE_TURNING_ON:
                                returnObj.put(KEY_CONNECT_CONN, "turning on");
                                break;
                            default:
                                returnObj.put(KEY_CONNECT_CONN, String.valueOf(iVal));
                                break;
                        }
                    }            
                }
                //Set scan mode
                switch(iVal = getScanMode(oBt)) {
                    case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
                        returnObj.put(KEY_CONNECT_SCAN, "connectable");
                        break;
                    case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
                        returnObj.put(KEY_CONNECT_SCAN, "connectable discoverable");
                        break;
                    case BluetoothAdapter.SCAN_MODE_NONE:
                        returnObj.put(KEY_CONNECT_SCAN, "none");
                        break;
                    case -1:
                        //Ignore the failure value
                        break;
                    default:
                        returnObj.put(KEY_CONNECT_SCAN, String.valueOf(iVal));
                        break;
                }
                //Set connection state for special peripheral
                if(m_oMetProfConn != null) {
                    Object oA2dp = null;
                    Object oHeadset = null;
                    Object oHealth = null;
                    //Query connection states
                    try {
                        oA2dp = m_oMetProfConn.invoke(oBt, BluetoothProfile.A2DP);
                        oHeadset = m_oMetProfConn.invoke(oBt, BluetoothProfile.HEADSET);
                        oHealth = m_oMetProfConn.invoke(oBt, BluetoothProfile.HEALTH);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    //Set A2DP 
                    if((oA2dp != null) && (oA2dp instanceof Integer)) {
                        switch(iVal = Integer.class.cast(oA2dp).intValue()) {
                            case BluetoothProfile.STATE_CONNECTED:
                                returnObj.put(KEY_CONNECT_A2DP, "connected");
                                break;
                            case BluetoothProfile.STATE_CONNECTING:
                                returnObj.put(KEY_CONNECT_A2DP, "connecting");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTED:
                                returnObj.put(KEY_CONNECT_A2DP, "disconnected");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTING:
                                returnObj.put(KEY_CONNECT_A2DP, "disconnecting");
                                break;
                            default:
                                returnObj.put(KEY_CONNECT_A2DP, String.valueOf(iVal));
                                break;
                        }
                    }
                    //Set headset
                    if((oHeadset != null) && (oHeadset instanceof Integer)) {
                        switch(iVal = Integer.class.cast(oHeadset).intValue()) {
                            case BluetoothProfile.STATE_CONNECTED:
                                returnObj.put(KEY_CONNECT_HEADSET, "connected");
                                break;
                            case BluetoothProfile.STATE_CONNECTING:
                                returnObj.put(KEY_CONNECT_HEADSET, "connecting");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTED:
                                returnObj.put(KEY_CONNECT_HEADSET, "disconnected");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTING:
                                returnObj.put(KEY_CONNECT_HEADSET, "disconnecting");
                                break;
                            default:
                                returnObj.put(KEY_CONNECT_HEADSET, String.valueOf(iVal));
                                break;
                        }
                    }
                    //Set health
                    if((oHealth != null) && (oHealth instanceof Integer)) {
                        switch(iVal = Integer.class.cast(oHealth).intValue()) {
                            case BluetoothProfile.STATE_CONNECTED:
                                returnObj.put(KEY_CONNECT_HEALTH, "connected");
                                break;
                            case BluetoothProfile.STATE_CONNECTING:
                                returnObj.put(KEY_CONNECT_HEALTH, "connecting");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTED:
                                returnObj.put(KEY_CONNECT_HEALTH, "disconnected");
                                break;
                            case BluetoothProfile.STATE_DISCONNECTING:
                                returnObj.put(KEY_CONNECT_HEALTH, "disconnecting");
                                break;
                            default:
                                returnObj.put(KEY_CONNECT_HEALTH, String.valueOf(iVal));
                                break;
                        }
                    }
                }
            }
        }
        else if(aDebug.ENABLED)
            aDebug.w(LOG_TAG, String.format("Invalid group: %d", group));
        return returnObj;
    }
    /**Get bluetooth data by group set.
     * @param context   [in] Application environment.
     * @param group     [in] Identify of group, this parameter should be one of GROUP_xxx 
     *                  definition.
     * @param di        [out] Repository of device information. Set null if without repository to
     *                  feed.<p>
     *                  This parameter is valid if group is GROUP_CAPABILITY only, because this is 
     *                  the only group with data to server. 
     * @param preview   [in] Set true if require data updated to server, set false if not. 
     * @return          Return data set if succeeded, return null if not available.*/
    public static HashMap<String, String> getBtData(Context context, int group, boolean preview)
    {
        return getBtData(context, group, (preview? DeviceInfo.GetInstance(context): null));
    }
    
    /**Get sorted key of data.
     * @param data  [in] Bluetooth data which returned from getBtData or getBtDevData.
     * @return      Return list of sorted key of data if succeeded, return null if failed.*/
    public static String[] getSortedKeyOfBtData(HashMap<String, String> data)
    {
        String      returnObj[] = null;
        Set<String> oKeys = null;
        if((data != null) && ((oKeys = data.keySet()) != null)) {
            returnObj = oKeys.toArray(new String[oKeys.size()]);
            Arrays.sort(returnObj, new KeyComp());
        }
        return returnObj;
    }
    
    /**Get bonded bluetooth devices' data set.
     * @return  Return arrays for devices if succeeded, return empty array if without devices, else
     *          return null if failed.<p>
     *          Each elements in returned array is an data set for one device.*/
    public static ArrayList<HashMap<String, String>> getBtDevData()
    {
        ArrayList<HashMap<String, String>>  returnObj = null;
        BluetoothAdapter                    oBt = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice>                listDev  = null;
        if(oBt == null) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, "Without BluetoothAdapter");
        }
        //Handle without device
        else if(((listDev = getBtDevs(oBt)) == null) || (listDev.isEmpty())) {
            returnObj = new ArrayList<HashMap<String, String>> ();
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without BluetoothDevice");
        }
        //Handle devices one by one
        else {
            Iterator<BluetoothDevice>   oItr = listDev.iterator();
            returnObj = new ArrayList<HashMap<String, String>> ();
            while(oItr.hasNext()) {
                BluetoothDevice oDev = oItr.next();
                if(oDev != null) {
                    HashMap<String, String> oInfo = new HashMap<String, String> ();
                    BtDevHelper.putDevIntoDataSet(oDev, oInfo);
                    if(!oInfo.isEmpty())
                        returnObj.add(oInfo);
                }
            }
        }
        return returnObj;
    }    
    
    /**Wrap function for get bluetooth name with try/catch block, to deal with exception if 
     * permission BLUETOOTH removed.
     * @param oBt   [in] Bluetooth instance.
     * @return      Return bluetooth name if succeeded, return null if failed.*/
    private static String getName(BluetoothAdapter oBt)
    {
        String  returnObj = null;
        if(oBt != null) {
            try {
                returnObj = oBt.getName();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }
        return returnObj;
    }
    
    /**Wrap function for get bluetooth MAC address with try/catch block, to deal with exception if 
     * permission BLUETOOTH removed.
     * @param oBt   [in] Bluetooth instance.
     * @return      Return MAC address of bluetooth if succeeded, return null if failed.*/
    private static String getAddress(BluetoothAdapter oBt)
    {
        String  returnObj = null;
        if(oBt != null) {
            try {
                returnObj = oBt.getAddress();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }
        return returnObj;
    }
    
    /**Wrap function for get bluetooth states with try/catch block, to deal with exception if 
     * permission BLUETOOTH removed.
     * @param oBt   [in] Bluetooth instance.
     * @return      Return one of STATE_xxx definition at BluetoothAdapter if succeeded, return 
     *              negative if failed.*/
    private static int getBtState(BluetoothAdapter oBt) {
        int returnVal = -1;
        if(oBt != null) {
            try {
                returnVal = oBt.getState();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }        
        return returnVal;
    }
    
    /**Wrap function for get scan mode with try/catch block, to deal with exception if permission 
     * BLUETOOTH removed.
     * @param oBt   [in] Bluetooth instance.
     * @return      Return one of STATE_xxx definition at BluetoothAdapter if succeeded, return 
     *              negative if failed.*/
    private static int getScanMode(BluetoothAdapter oBt) {
        int returnVal = -1;
        if(oBt != null) {
            try {
                returnVal = oBt.getScanMode();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }        
        return returnVal;
    }
    
    /**Wrap function for get bluetooth devices with try/catch block, to deal with exception if 
     * permission BLUETOOTH removed.
     * @param oBt   [in] Bluetooth instance.
     * @return      Return list of bounded bluetooth devices if succeeded, return null if failed.*/
    private static Set<BluetoothDevice> getBtDevs(BluetoothAdapter oBt) {
        Set<BluetoothDevice>    returnObj = null;
        if(oBt != null) {
            try {
                returnObj = oBt.getBondedDevices();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }        
        return returnObj;
    }
    
    /**Check if MAC address is valid.
     * @param szMac [in] MAC address.
     * @return      Return true if MAC address is valid, return false if not.*/
    protected static boolean isValidMacAddress(String szMac)
    {
        boolean returnVal = false;
        if((szMac != null) && (szMac.length() == 17)) {
            char    chMac[] = szMac.toCharArray();
            returnVal = true;            
            for(int a = 16; ((a >= 0) && returnVal); a --) {
                if((a % 3) == 2)
                    returnVal = (chMac[a] == ':');
                else
                    returnVal = (((chMac[a] >= 'a') && (chMac[a] <= 'f')) || ((chMac[a] >= 'A') && (chMac[a] <= 'F')) || ((chMac[a] >= '0') && (chMac[a] <= '9')));
            }
        }
        return returnVal;
    }
        
    /**Load hidden API.*/
    private static void loadHiddenApi()
    {
        //Load hidden API in WifiManager
        if(!m_bLoadApi) {
            try {
                m_oMetTimeout = BluetoothAdapter.class.getMethod("getDiscoverableTimeout");
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            }
            try {
                m_oMetConn = BluetoothAdapter.class.getMethod("getConnectionState");
                m_oMetProfConn = BluetoothAdapter.class.getMethod("getProfileConnectionState", Integer.TYPE);
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            } 
            try {
                m_oClsProp = Class.forName("android.net.LinkProperties");
                m_oMetProp = ConnectivityManager.class.getMethod("getLinkProperties", Integer.TYPE);
                m_oMetInterface = m_oClsProp.getMethod("getInterfaceName");
            }
            catch(Exception ex) {
                m_oClsProp = null;
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            }
            m_bLoadApi = true;
        }
    }
    
    
    /**Key comparator.*/
    private static class KeyComp implements Comparator<String>
    {
        /**Set of keys' priority.*/
        private static KeySet   KEYS[] = {
                       //Key name                   //Priority
            new KeySet(KEY_CAPABILITY_BT_AVA,       0),
            new KeySet(BtDevHelper.KEY_NAME,        1), //KEY_CAPABILITY_NAME
            new KeySet(BtDevHelper.KEY_ALIAS,       2), //BtDevHelper.KEY_ALIASNAME
            new KeySet(KEY_CAPABILITY_STATE,        3),
            new KeySet(BtDevHelper.KEY_ADDR,        4), //KEY_CAPABILITY_ADDR
            new KeySet(KEY_CAPABILITY_INTERFACE,    5), //This field not used at bluetooth
            new KeySet(KEY_CAPABILITY_TIMEOUT,      6),
            new KeySet(KEY_CONNECT_SCAN,            7),
            new KeySet(KEY_CONNECT_CONN,            8),
            new KeySet(KEY_CONNECT_A2DP,            9),
            new KeySet(KEY_CONNECT_HEADSET,         10),
            new KeySet(KEY_CONNECT_HEALTH,          11),
            new KeySet(BtDevHelper.KEY_CLS_MAJOR,   12),
            new KeySet(BtDevHelper.KEY_CLS,         13),
            new KeySet(BtDevHelper.KEY_SERV,        14),
            new KeySet(BtDevHelper.KEY_BOND,        15),
            new KeySet(BtDevHelper.KEY_TRUST,       16),
            new KeySet(BtDevHelper.KEY_DOCK,        17)
        };
        /**Flag to indicates if KEYS field be sorted.*/
        private static boolean  m_bSorted = false;
        
        /**Compares its two arguments for order. Returns a negative integer, zero, or a positive 
         * integer as the first argument is less than, equal to, or greater than the second.
         * @param o1    [in] the first object to be compared.
         * @param o2    [in] the second object to be compared.
         * @return      a negative integer, zero, or a positive integer as the first argument is 
         *              less than, equal to, or greater than the second.*/
        @Override
        public int compare(String o1, String o2)
        {
            int returnVal = 0;
            //Sort key set
            if(!m_bSorted) {
                Arrays.sort(KEYS);
                m_bSorted = true;
            }
            //Handle if key is null
            if(o1 == null)
                returnVal = ((o2 == null)? 0: 1);
            else if(o2 == null)
                returnVal = -1;
            else {

                int iIdx1 = -1;
                int iIdx2 = -1;
                //Get index of keys
                iIdx1 = Arrays.binarySearch(KEYS, new KeySet(o1));
                iIdx2 = Arrays.binarySearch(KEYS, new KeySet(o2));
                //Handle if key not found
                if(iIdx1 < 0)
                    returnVal = ((iIdx2 < 0)? o1.compareToIgnoreCase(o2): 1);
                else if(iIdx2 < 0)
                    returnVal = -1;
                else if(iIdx1 == iIdx2)
                    returnVal = 0;
                else
                    returnVal = ((KEYS[iIdx1].getPrio() < KEYS[iIdx2].getPrio())? -1: 1);
            }
            return returnVal;
        }
    };
    
    
    /**Key set for KeyComp.*/
    protected static class KeySet implements Comparable<KeySet>
    {
        /**Key name.*/
        private String  m_szName = null;
        /**Priority.
         * This field should be an unique and continue index from zero.*/
        private int     m_iPriority = -1;
        
        /**Constructor.
         * @param szName    [in] Name of key.
         * @param iPriority [in] Priority of key, this parameter be an unique and continue index 
         *                  from zero.*/
        protected KeySet(String szName, int iPriority)
        {
            m_szName = szName;
            m_iPriority = iPriority;
        }
        /**Constructor.<p>
         * This constructor created item for temporary instance only. 
         * @param szName    [in] Name of key.*/
        protected KeySet(String szName)
        {
            this(szName, -1);
        }
        
        /**Compares this object with the specified object for order. Returns a negative integer, 
         * zero, or a positive integer as this object is less than, equal to, or greater than the 
         * specified object.
         * @param o [in] the object to be compared.
         * @return  a negative integer, zero, or a positive integer as this object is less than, 
         *          equal to, or greater than the specified object.*/
        @Override
        public int compareTo(KeySet o)
        {
            int returnVal = 0;
            if(m_szName == null)
                returnVal = (((o == null) || (o.m_szName == null))? 0: 1);
            else if((o == null) || (o.m_szName == null))
                returnVal = -1;
            else
                returnVal = m_szName.compareToIgnoreCase(o.m_szName);
            return returnVal;
        }
        
        /**Get priority.
         * @return  Return priority.*/
        protected int getPrio()
        {
            return m_iPriority;
        }
    };
    
    
    /**Agent to translates BluetoothDevice into data set.*/
    private static class BtDevHelper
    {
        /**Key of name.*/
        private static final String KEY_NAME = "Name";
        /**Key of address.*/
        private static final String KEY_ADDR = "MAC";
        /**Key of bond state.*/
        private static final String KEY_BOND = "Bond";
        /**Key of alias.*/
        private static final String KEY_ALIAS = "Alias";
        /**Key of alias name.*/
        private static final String KEY_ALIASNAME = "Alias";
        /**Key of trusted state.*/
        private static final String KEY_TRUST = "Trusted";
        /**Key of dock state.*/
        private static final String KEY_DOCK = "Is dock";
        /**Major category.*/
        private static final String KEY_CLS_MAJOR = "Major category";
        /**Category.*/
        private static final String KEY_CLS = "Category";
        /**Supported service.*/
        private static final String KEY_SERV = "Function";
        /**Hidden method to query alias.*/
        private static Method       MET_ALIAS = null;
        /**Hidden method to query alias name.*/
        private static Method       MET_ALIASNAME = null;
        /**Hidden method to check if device trusted.*/
        private static Method       MET_TRUST = null;
        /**Hidden method to check if device is dock.*/
        private static Method       MET_DOCK = null;
        /**Fields of major category which defined at BluetoothClass.Device.Major.*/
        private static Field        FIELD_CLS_MAJOR[] = null;
        /**Fields of category which defined at BluetoothClass.Device.*/
        private static Field        FIELD_CLS[] = null;
        /**Fields of service which defined at BluetoothClass.Service.*/
        private static Field        FIELD_SERV[] = null;
        /**Flag to indicates if hidden API loaded.*/
        private static boolean      m_bLoad = false;
        
        /**Put device information into data set.
         * @param dev   [in] Device information.
         * @param data  [out] Data set to contains device information.
         * @return      Return instance of data set if succeeded, return null if failed.*/
        private static HashMap<String, String> putDevIntoDataSet(BluetoothDevice dev, HashMap<String, String> data)
        {
            HashMap<String, String> returnObj = null;
            loadHiddenBtDev();
            if(dev == null) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Without BluetoothDevice");
            }
            else if(data == null) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Without HashMap for data");
            }
            else {
                String          szVal = null;
                BluetoothClass  oCls = null;
                returnObj = data;
                //Device name
                if(((szVal = dev.getName()) != null) && (szVal.length() > 0))
                    returnObj.put(KEY_NAME, szVal);
                //Device address
                if(((szVal = dev.getAddress()) != null) && (szVal.length() > 0))
                    returnObj.put(KEY_ADDR, szVal);
                
                //Device bond
                switch(dev.getBondState()) {
                    case BluetoothDevice.BOND_BONDED:
                        returnObj.put(KEY_BOND, "bonded");
                        break;
                    case BluetoothDevice.BOND_BONDING:
                        returnObj.put(KEY_BOND, "bonding");
                        break;
                    case BluetoothDevice.BOND_NONE:
                        returnObj.put(KEY_BOND, "none");
                        break;
                }
                //Alias
                if(MET_ALIAS != null) {
                    Object  oVal = null;
                    try {
                        oVal = MET_ALIAS.invoke(dev);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && ((szVal = oVal.toString()).length() > 0))
                        returnObj.put(KEY_ALIAS, szVal);
                }
                if(MET_ALIASNAME != null) {
                    Object  oVal = null;
                    try {
                        oVal = MET_ALIASNAME.invoke(dev);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && ((szVal = oVal.toString()).length() > 0))
                        returnObj.put(KEY_ALIASNAME, szVal);
                }
                //Trusted state
                if(MET_TRUST != null) {
                    Object  oVal = null;
                    try {
                        oVal = MET_TRUST.invoke(dev);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Boolean))
                        returnObj.put(KEY_TRUST, oVal.toString());
                }
                //Dock state
                if(MET_DOCK != null) {
                    Object  oVal = null;
                    try {
                        oVal = MET_DOCK.invoke(dev);
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Boolean))
                        returnObj.put(KEY_DOCK, oVal.toString());
                }
                //Categories
                if((oCls = dev.getBluetoothClass()) != null) {
                    int iCls = -1;
                    //Major category
                    if((FIELD_CLS_MAJOR != null) && ((iCls = oCls.getMajorDeviceClass()) >= 0)) {
                        //Compares major category with loaded field one by one 
                        for(Field oField: FIELD_CLS_MAJOR) {
                            int iVal = -1;
                            try {
                                iVal = oField.getInt(null);
                            }
                            catch(Exception ex) {
                                if(aDebug.ENABLED)
                                    aDebug.w(LOG_TAG, "Get value failed", ex);
                            }
                            if(iVal == iCls) {
                                returnObj.put(KEY_CLS_MAJOR, oField.getName());
                                break;
                            }
                        }
                    }
                    //Category
                    if((FIELD_CLS != null) && ((iCls = oCls.getDeviceClass()) >= 0)) {
                        //Compares category with loaded field one by one
                        for(Field oField: FIELD_CLS) {
                            int iVal = -1;
                            try {
                                iVal = oField.getInt(null);
                            }
                            catch(Exception ex) {
                                if(aDebug.ENABLED)
                                    aDebug.w(LOG_TAG, "Get value failed", ex);
                            }
                            if(iVal == iCls) {
                                returnObj.put(KEY_CLS, oField.getName());
                                break;
                            }
                        }
                    }
                    //Service
                    if(FIELD_SERV != null) {
                        StringBuffer    szServ = new StringBuffer();
                        for(Field oField: FIELD_SERV) {
                            int iVal = -1;
                            try {
                                iVal = oField.getInt(null);
                            }
                            catch(Exception ex) {
                                if(aDebug.ENABLED)
                                    aDebug.w(LOG_TAG, "Get value failed", ex);
                            }
                            if(oCls.hasService(iVal)) {
                                if(szServ.length() > 0)
                                    szServ.append(",");
                                szServ.append(oField.getName());
                            }
                        }
                        if(szServ.length() > 0)
                            returnObj.put(KEY_SERV, szServ.toString());
                    }
                }
                returnObj = data; 
            }
            return returnObj;
        }
        
        /**Load hidden API in BluetoothDevice.*/
        @SuppressLint("NewApi")
        private static void loadHiddenBtDev()
        {
            if(!m_bLoad) {
                Field   oFields[] = null;
                try {
                    MET_ALIAS = BluetoothDevice.class.getMethod("getAlias");
                }
                catch(Exception ex) {
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                try {
                    MET_ALIASNAME = BluetoothDevice.class.getMethod("getAliasName");
                }
                catch(Exception ex) {
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                try {
                    MET_TRUST = BluetoothDevice.class.getMethod("getTrustState");
                }
                catch(Exception ex) {
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                try {
                    MET_DOCK = BluetoothDevice.class.getMethod("isBluetoothDock");
                }
                catch(Exception ex) {
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                //Load definition for major category from BluetoothClass.Device.Major
                try {
                    oFields = BluetoothClass.Device.Major.class.getDeclaredFields();
                }
                catch(Exception ex) {
                    oFields = null;
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                if(oFields != null) {
                    ArrayList<Field>    listField = new ArrayList<Field> ();
                    //Filter fields one by one
                    for(Field oField: oFields) {
                        int iMod = oField.getModifiers();
                        if(Modifier.isFinal(iMod) && Modifier.isStatic(iMod) && Integer.TYPE.equals(oField.getType()))
                            listField.add(oField);
                    }
                    if(!listField.isEmpty())
                        FIELD_CLS_MAJOR = listField.toArray(new Field[listField.size()]);
                }
                //Load definition for category from BluetoothClass.Device
                try {
                    oFields = BluetoothClass.Device.class.getDeclaredFields();
                }
                catch(Exception ex) {
                    oFields = null;
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                if(oFields != null) {
                    ArrayList<Field>    listField = new ArrayList<Field> ();
                    //Filter fields one by one
                    for(Field oField: oFields) {
                        int iMod = oField.getModifiers();
                        if(Modifier.isFinal(iMod) && Modifier.isStatic(iMod) && Integer.TYPE.equals(oField.getType()))
                            listField.add(oField);
                    }
                    if(!listField.isEmpty())
                        FIELD_CLS = listField.toArray(new Field[listField.size()]);
                }
                //Load definition for service from BluetoothClass.Service
                try {
                    oFields = BluetoothClass.Service.class.getDeclaredFields();
                }
                catch(Exception ex) {
                    oFields = null;
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, "Load API failed", ex);
                }
                if(oFields != null) {
                    ArrayList<Field>    listField = new ArrayList<Field> ();
                    //Filter fields one by one
                    for(Field oField: oFields) {
                        int iMod = oField.getModifiers();
                        if(Modifier.isFinal(iMod) && Modifier.isStatic(iMod) && Integer.TYPE.equals(oField.getType()))
                            listField.add(oField);
                    }
                    if(!listField.isEmpty())
                        FIELD_SERV = listField.toArray(new Field[listField.size()]);
                }
                m_bLoad = true;
            }
        }
    };
}
